/*
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pps.gl.server;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import org.pps.gl.client.GetInfoService;
import org.pps.gl.client.LoginInfo;
import org.pps.gl.client.data.EtudeInfo;
import org.pps.gl.client.data.ExperienceInfo;
import org.pps.gl.client.data.MessageInfo;
import org.pps.gl.client.data.NewsInfo;
import org.pps.gl.client.data.UserInfo;
import org.pps.gl.server.datastore.Ecole;
import org.pps.gl.server.datastore.Entreprise;
import org.pps.gl.server.datastore.Etude;
import org.pps.gl.server.datastore.EventLogger;
import org.pps.gl.server.datastore.Experience_Pro;
import org.pps.gl.server.datastore.MessagePrive;
import org.pps.gl.server.datastore.Profil;
import org.pps.gl.server.datastore.Specialite;
import org.pps.gl.server.datastore.Utilisateur;

import com.google.appengine.api.datastore.Entity;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class GetInfoServiceImpl extends RemoteServiceServlet implements
		GetInfoService {

	private static final long serialVersionUID = -2549432354326879466L;
	private static final double pondContact = 0.3;
	private static final double pondEtude = 0.5;
	private static final double pondExperience = 0.2;
	private static final int profondeurRecursif = 3;
	
	@Override
	public UserInfo getUserIdentite(String email) {

		Entity user = Utilisateur.getUtilisateur(email);
		String lastName = Utilisateur.getNom(user);
		String firstName = Utilisateur.getPrenom(user);
		String dateN = Utilisateur.getDateNaissance(user);
		String sexe = Utilisateur.getSexe(user);
		String statut = Profil.getStatut(Utilisateur.getProfil(user));

		// On récupère les études de l'utilisateur
		Iterable<Entity> sesEtudes = Etude.getEtudesForProfil(email);
		ArrayList<EtudeInfo> listeEtude = new ArrayList<EtudeInfo>();
		Entity ecole;
		EtudeInfo etudeInfo;

		// On récupère les études
		if (sesEtudes != null) {
			for (Entity etude : sesEtudes) {
				etudeInfo = new EtudeInfo();
				etudeInfo.setPeriode(Etude.getPeriodeEtude(etude));
				etudeInfo.setIdEtude(Etude.getIdEtude(etude));
				ecole = Ecole.getEcole(etude);
				etudeInfo.setNomEcole(Ecole.getNomEcole(ecole));
				etudeInfo.setSpecialite(Specialite.getNomSpecialite(Specialite
						.getSpecialite(email)));
				listeEtude.add(etudeInfo);
			}
		}

		// On récupère les experiences pro
		Iterable<Entity> sesExperiences = Experience_Pro
				.getExperienceForUser(email);
		ArrayList<ExperienceInfo> listeExperiences = new ArrayList<ExperienceInfo>();
		ExperienceInfo expInfo;
		Entity ent;

		if (sesExperiences != null)
			for (Entity experience : sesExperiences) {
				expInfo = new ExperienceInfo();
				expInfo.setPeriode(Experience_Pro
						.getPeriodeExperience(experience));
				expInfo.setDirection(Experience_Pro.getDirection(experience));
				expInfo.setPoste(Experience_Pro.getPoste(experience));
				expInfo.setDescription(Experience_Pro
						.getDescription(experience));
				expInfo.setDomaine(Experience_Pro.getDomaine(experience));
				expInfo.setExperienceId(Experience_Pro
						.getIdExperience(experience));

				ent = Entreprise.getEntreprise(experience);
				expInfo.setNomEntreprise(Entreprise.getNomEntreprise(ent));
				listeExperiences.add(expInfo);
			}

		UserInfo info = new UserInfo(email, lastName, firstName, dateN, sexe,
				statut, listeEtude, listeExperiences);
		info.setContacts(Utilisateur.getAllContacts(user));
		return info;
	}

	@Override
	public HashMap<UserInfo, Integer> doSearch(String mail, String email,
			String nomprenom, String societe, String ecole) {

		HashMap<UserInfo, Integer> resultat = new HashMap<UserInfo, Integer>();
		HashMap<String, Integer> map = new HashMap<String, Integer>();
		// score normalisé
		HashMap<String, Double> mapNorm = new HashMap<String, Double>();
		// avoir la distance entre le contact trouvé et nous
		HashMap<String, Integer> distContact = new HashMap<String, Integer>();

		// Recherche par email
		if (!email.isEmpty()) {
			Entity user = Utilisateur.getUtilisateur(email);
			if (user != null) {
				map.put(email, 100);
			}
		}

		// Recherche par nom ou prénom
		if (!nomprenom.isEmpty()) {
			Iterable<Entity> users = Utilisateur.getAllUtilisateur();
			String[] tab = nomprenom.split(" ");
			for (Entity u : users) {
				for (String s : tab) {
					if (u.getProperty("nom").toString().equalsIgnoreCase(s)
							|| u.getProperty("prenom").toString()
									.equalsIgnoreCase(s)) {
						// Entity p = Utilisateur.getProfil(u);
						Integer x = map.get(u.getKey().getName());
						map.put(u.getKey().getName(), (x == null) ? 10 : x + 10);
					}
				}
			}
		}

		// Recherche par société
		if (!societe.isEmpty()) {
			// on récupère toutes les entreprises avec le nom
			Iterable<Entity> ent = Utilisateur.getSocieteByName(societe);
			for (Entity u : ent) {
				// on cherche tous les utilisateurs ayant une expérience avec
				// cette entreprise
				Integer x = map.get(u.getProperty("email_user").toString());
				map.put(u.getProperty("email_user").toString(),
						(x == null) ? 10 : x + 10);
			}
		}

		// Recherche par école
		if (!ecole.isEmpty()) {
			// on récupère toutes les écoles avec le nom
			Iterable<Entity> eco = Utilisateur.getEcoleByName(ecole);
			if (eco != null)
				for (Entity u : eco) {
					// on cherche tous les utilisateurs ayant une expérience
					// avec cette école
					Integer x = map.get(u.getProperty("email_user").toString());
					map.put(u.getProperty("email_user").toString(),
							(x == null) ? 10 : x + 10);
				}
			Iterable<Entity> spe = Utilisateur.getSpecialiteByName(ecole);
			if (spe != null)
				for (Entity u : spe) {
					// on cherche tous les utilisateurs ayant une expérience
					// avec cette spécialité
					Integer x = map.get(u.getProperty("email_user").toString());
					map.put(u.getProperty("email_user").toString(),
							(x == null) ? 10 : x + 10);
				}
		}

		// on parcourt tous les potentiels et on compare leur expérience avec
		// l'utilisateur
		int scoreContact = 0, scoreMaxContact = 0;
		for (String u : map.keySet()) {
			scoreContact = getScoreContact(mail, u);
			mapNorm.put(u, (double) scoreContact);
			if (scoreContact > scoreMaxContact)
				scoreMaxContact = scoreContact;
		}
		// on normalise par le nombre de contact en commun maximum
		for (String u : mapNorm.keySet()) {
			double w = mapNorm.get(u);
			mapNorm.put(u, (double) w * pondContact / scoreMaxContact);
		}

		double score;
		for (String u : mapNorm.keySet()) {
			score = mapNorm.get(u);
			score += pondExperience
					* Experience_Pro.getScoreExperience(mail, u);
			score += pondEtude * Etude.getScoreEtude(mail, u);
			// on pondère nos résultats
			if (score > 0)
				mapNorm.put(u, map.get(u) * score);
			else
				mapNorm.put(u, map.get(u) * 0.001);
			if (u.equals(mail))
				mapNorm.put(u, mapNorm.get(u) * 0.001);
		}

		// Ajout des entrées de la map à une liste
		final List<Entry<String, Double>> entries = new ArrayList<Entry<String, Double>>(
				mapNorm.entrySet());
		// Tri de la liste sur la valeur de l'entrée
		Collections.sort(entries, new Comparator<Entry<String, Double>>() {
			public int compare(final Entry<String, Double> e1,
					final Entry<String, Double> e2) {
				return -e1.getValue().compareTo(-e2.getValue());
			}
		});
		
		// distance entre les contacts de la recherche et l'utilisateur
		ArrayList<String> dejavu = new ArrayList<String>();
		dejavu.add(mail);
		distContact = getRecursiveSuggestionsContacts(
				dejavu, mail, 0, profondeurRecursif);
		for (String mapKey : distContact.keySet()) {
			distContact.put(mapKey, (1+distContact.get(mapKey)));
		}
		distContact.put(mail, 0); // on ajoute l'utilisateur
		// on ajoute ses contacts
		ArrayList<String> cntcts = Utilisateur.getAllContacts(Utilisateur
				.getUtilisateur(mail));
		for (String c : cntcts)
			distContact.put(c, 1);

		// On trie les résultats en fonction de leur score
		for (final Entry<String, Double> entry : entries) {
			Entity user = Utilisateur.getUtilisateur(entry.getKey());
			Entity p = Utilisateur.getProfil(user);
			UserInfo uinfo = new UserInfo(entry.getKey(), user.getProperty(
					"prenom").toString(), user.getProperty("nom").toString(),
					user.getProperty("dateNaissance").toString(), user
							.getProperty("sexe").toString(), p.getProperty(
							"statut").toString(), null, null);
			if (distContact.containsKey(entry.getKey()))
				resultat.put(uinfo, distContact.get(entry.getKey()));
			else
				resultat.put(uinfo, 9);
		}

		return resultat;
	}

	public boolean majIdentite(String email, String firstName, String lastName,
			String statut, String sexe, String dateN) {
		boolean modify = false;
		if (null != firstName) {
			Utilisateur.updateUser(email, "prenom", firstName);
			modify = true;
		}

		if (null != lastName) {
			Utilisateur.updateUser(email, "nom", lastName);
			modify = true;
		}

		if (null != statut) {
			Entity user = Utilisateur.getUtilisateur(email);
			Profil.updateProfil(Utilisateur.getProfil(user), "statut", statut);
			modify = true;
		}

		if (null != sexe) {
			Utilisateur.updateUser(email, "sexe", sexe);
			modify = true;
		}

		if (null != dateN) {
			Utilisateur.updateUser(email, "dateNaissance", dateN);
			modify = true;
		}
		if (modify) {
			Entity user = Utilisateur.getUtilisateur(email);
			Date d = new Date();
			ArrayList<String> usersEmail = new ArrayList<String>();
			usersEmail.add(email);
			EventLogger.createEventLogger(user, d,
					EventLogger.EVENT_MODIFY_USER_INFORMATION, usersEmail);
		}

		return true;
	}

	@Override
	public LoginInfo getUserLoginInfo(LoginInfo currentUser) {

		Entity user = Utilisateur.getUtilisateur(currentUser.getEmailAddress());
		currentUser = Infos.getInfo(currentUser, user);
		return currentUser;
	}

	/**
	 * Méthode qui permet d'insérer une periode d'étude pour un utilisateur.
	 * 
	 * @param emailUser
	 * @param debut
	 * @param fin
	 * @param nomEcole
	 * @param specialite
	 * @return
	 */
	public boolean addEtude(String emailUser, String debut, String fin,
			String nomEcole, String specialite) {

		Entity profil = Utilisateur.getProfil(Utilisateur
				.getUtilisateur(emailUser));
		Entity etude = Etude.createEtude(profil, debut + "-" + fin, emailUser);
		Entity ecole = Ecole.createEcole(etude, emailUser, nomEcole);
		Specialite.createSpecialite(ecole, emailUser, specialite);

		Entity user = Utilisateur.getUtilisateur(emailUser);
		Date d = new Date();
		ArrayList<String> usersEmail = new ArrayList<String>();
		usersEmail.add(emailUser);
		EventLogger.createEventLogger(user, d, EventLogger.EVENT_MODIFY_ETUDE,
				usersEmail);
		return true;

	}

	@Override
	public boolean createContactDemande(String emailDemandeur,
			String emailReceiver) {
		return Utilisateur.createContactDemande(emailDemandeur, emailReceiver);
	}

	public boolean confirmContactDemande(String emailDemandeur,
			String emailReceiver) {
		return Utilisateur.confirmContact(emailDemandeur, emailReceiver);
	}

	/**
	 * Méthode qui permet d'insérer une experience pour un utilisateur dans le
	 * datastore.
	 */
	public boolean addExperience(String emailAddress, String debut, String fin,
			String nomEntreprise, String direction, String poste,
			String description, String domaine) {

		Entity experience = Experience_Pro.createExperience(emailAddress, debut
				+ "-" + fin, direction, poste, description, domaine);
		Entreprise.createEntreprise(experience, emailAddress, nomEntreprise);

		Entity user = Utilisateur.getUtilisateur(emailAddress);
		Date d = new Date();
		ArrayList<String> usersEmail = new ArrayList<String>();
		usersEmail.add(emailAddress);
		EventLogger.createEventLogger(user, d,
				EventLogger.EVENT_MODIFY_EXPERIENCE, usersEmail);

		return true;
	}

	@Override
	/**
	 * @return une liste des news vide ou non (mais jamais null);
	 */
	public ArrayList<NewsInfo> getNews(String userMail) {
		Entity user = Utilisateur.getUtilisateur(userMail);
		if (user == null) {
			return new ArrayList<NewsInfo>();
		}
		return Utilisateur.getNews(user);
	}

	/**
	 * Méthode pour les suggestions de contact
	 * 
	 * @see org.pps.gl.client.GetInfoService#doSearch(java.lang.String,
	 *      java.lang.String, java.lang.String, java.lang.String)
	 */
	public HashMap<UserInfo, Integer> doSuggestion(String userMail) {
		// résultat à retourner
		HashMap<UserInfo, Integer> resultat = new HashMap<UserInfo, Integer>();
		HashMap<String, Double> mapNorm = new HashMap<String, Double>();
		// avoir la distance entre le contact trouvé et l'utilisateur
		HashMap<String, Integer> distContact = new HashMap<String, Integer>();

		Entity me = Utilisateur.getUtilisateur(userMail);
		ArrayList<Entity> allUsers = new ArrayList<Entity>();

		// on récupère tous les utilisateurs, sauf l'utilisateur et ses contacts
		for (Entity e : Utilisateur.getAllUtilisateur()) {
			if (!e.getKey().getName().equals(userMail)
					&& !Utilisateur.getAllContacts(me).contains(
							e.getKey().getName()))
				allUsers.add(e);
		}

		// on parcourt tous les potentiels et on compare leur expérience avec
		// l'utilisateur
		int scoreContact = 0, scoreMaxContact = 0;
		for (Entity u : allUsers) {
			scoreContact = getScoreContact(userMail, u.getKey().getName());
			mapNorm.put(u.getKey().getName(), (double) scoreContact);
			if (scoreContact > scoreMaxContact)
				scoreMaxContact = scoreContact;
		}
		// on normalise par le nombre de contact en commun maximum
		for (Entity u : allUsers) {
			double w = mapNorm.get(u.getKey().getName());
			mapNorm.put(u.getKey().getName(), (double) w * pondContact
					/ scoreMaxContact);
		}

		double score;
		for (Entity u : allUsers) {
			score = mapNorm.get(u.getKey().getName());
			score += pondExperience
					* Experience_Pro.getScoreExperience(userMail, u.getKey()
							.getName());
			score += pondEtude
					* Etude.getScoreEtude(userMail, u.getKey().getName());
			if (score > 0)
				mapNorm.put(u.getKey().getName(), score);
			else
				mapNorm.put(u.getKey().getName(), 0.001);
		}

		// distance entre les contacts potentiels et l'utilisateur
		ArrayList<String> dejavu = new ArrayList<String>();
		dejavu.add(userMail);
		distContact = getRecursiveSuggestionsContacts(
				dejavu, userMail, 0, profondeurRecursif);

		// Ajout des entrées de la map à une liste
		final List<Entry<String, Double>> entries = new ArrayList<Entry<String, Double>>(
				mapNorm.entrySet());
		// Tri de la liste sur le score
		Collections.sort(entries, new Comparator<Entry<String, Double>>() {
			public int compare(final Entry<String, Double> e1,
					final Entry<String, Double> e2) {
				return -e1.getValue().compareTo(-e2.getValue());
			}
		});
		// On trie les résultats en fonction de leur score
		for (final Entry<String, Double> entry : entries) {
			Entity user = Utilisateur.getUtilisateur(entry.getKey());
			Entity p = Utilisateur.getProfil(user);
			UserInfo uinfo = new UserInfo(entry.getKey(), user.getProperty(
					"prenom").toString(), user.getProperty("nom").toString(),
					user.getProperty("dateNaissance").toString(), user
							.getProperty("sexe").toString(), p.getProperty(
							"statut").toString(), null, null);
			if (distContact.containsKey(entry.getKey()))
				resultat.put(uinfo, (1+distContact.get(entry.getKey())));
			else
				resultat.put(uinfo, 9);
		}
		// on retourne les résultats avec leur distance
		return resultat;
	}

	/**
	 * Méthode qui calcule le nombre de contact en commun entre deux
	 * utilisateurs
	 */
	public int getScoreContact(String userMail, String autre) {
		ArrayList<String> ctc = Utilisateur.getAllContacts(Utilisateur
				.getUtilisateur(userMail));
		ArrayList<String> ctc1 = Utilisateur.getAllContacts(Utilisateur
				.getUtilisateur(autre));
		int i = 0;
		for (String c : ctc)
			for (String c1 : ctc1)
				if (c.equals(c1))
					i++;
		return i;
	}

	/**
	 * Methode récursive qui va récupérer les contacts de contacts, en les
	 * organisant par niveau
	 * 
	 * @param dejavu
	 *            liste de contacts déjà rencontré auparavant (évite les
	 *            doublons)
	 * @param cont
	 *            le contact dont on va récupérer les contact
	 * @param depth
	 *            la profondeur courante
	 * @param maxdepth
	 *            la profondeur max
	 * @return Un ArrayList d'arraylist contenant les contacts du niveau n
	 */
	public HashMap<String, Integer> getRecursiveSuggestionsContacts(
			ArrayList<String> dejavu, String cont, int depth, int maxdepth) {
		
		HashMap<String, Integer> ret = new HashMap<String, Integer>();
		ArrayList<String> finalc = Utilisateur.getAllContacts(Utilisateur
				.getUtilisateur(cont));
		finalc.removeAll(dejavu);
		
		if (depth >= maxdepth) {
			for (String c : finalc) {
				if (ret.containsKey(c)) {
					if (depth < ret.get(c))
						ret.put(c, depth);
				}
				else ret.put(c, depth);
			}
			return ret;
		}
		
		dejavu.add(cont);
		
		for (String c : finalc) {
			HashMap<String, Integer> conts = getRecursiveSuggestionsContacts(
					dejavu, c, depth+1, maxdepth);
			for (String mapKey : conts.keySet()) {
				if (ret.containsKey(mapKey)) {
					if (conts.get(mapKey) < ret.get(mapKey)) {
						ret.put(mapKey, (depth+conts.get(mapKey)));
					}
				}
				else ret.put(mapKey, conts.get(mapKey));
			}
		}
		
		if (depth != 0) {
			for (String c : finalc) {
				if (ret.containsKey(c)) {
					if (depth < ret.get(c))
						ret.put(c, depth);
				}
				else ret.put(c, depth);
			}
		}
		
		System.out.println("cont= " + cont + " depth= " + depth + " ret= " + ret);
		return ret;
	}

	/**
	 * Méthode qui calcule le nombre de contacts du second niveau
	 * 
	 * @return le nombre
	 */
	public ArrayList<Integer> getNbrContactSecond(String email) {
		HashMap<String, Integer> resultat = new HashMap<String, Integer>();
		ArrayList<Integer> nbrContact = new ArrayList<Integer>();
		ArrayList<String> dejavu = new ArrayList<String>();
		Integer nbr2=0, nbr3=0, nbr4=0, nbr5=0;
		
		dejavu.add(email);
		
		Entity utilisateur = Utilisateur.getUtilisateur(email);
		nbrContact.add(Utilisateur.getAllContacts(utilisateur).size());
		
		resultat = getRecursiveSuggestionsContacts(
				dejavu, email, 0, profondeurRecursif);
		for (String mapKey : resultat.keySet()) {
			resultat.put(mapKey, (1+resultat.get(mapKey)));
		}
		resultat.put(email, 0); // on ajoute l'utilisateur
		// on ajoute ses contacts
		ArrayList<String> cntcts = Utilisateur.getAllContacts(Utilisateur
				.getUtilisateur(email));
		for (String c : cntcts)
			resultat.put(c, 1);
		
		
		Collection<Integer> distance = resultat.values();
		for (Integer i : distance) {
			if (i==2)
				nbr2++;
			else if (i==3)
				nbr3++;
			else if (i==4)
				nbr4++;
			else if (i==5)
				nbr5++;
		}
		
		nbrContact.add(nbr2);
		nbrContact.add(nbr3);
		nbrContact.add(nbr4);
		nbrContact.add(nbr5);
		
		return nbrContact;
	}

	/**
	 * Méthode qui ajoute un message dans la liste de message
	 */
	public boolean addMessage(String destinataire, String expediteur,
			String message, String titre) {
		@SuppressWarnings("unused")
		Entity messagePrive = MessagePrive.createMessage(destinataire,
				expediteur, message, titre);
		return true;
	}

	/**
	 * Méthode qui récupère tous les messages
	 */
	public ArrayList<MessageInfo> getMessage(String userMail) {

		Iterable<Entity> listeEntity = MessagePrive.getMessageForUser(userMail);

		ArrayList<MessageInfo> listeMessage = new ArrayList<MessageInfo>();
		for (Entity entity : listeEntity)
			listeMessage.add(new MessageInfo((String) entity
					.getProperty("destinataire"), (String) entity
					.getProperty("expediteur"), (String) entity
					.getProperty("message"), (String) entity
					.getProperty("titre")));

		return listeMessage;
	}

	/**
	 * Méthode qui effacera du datastore une experience précise.
	 */
	public boolean deleteExperienceUser(long idExperience) {
		Experience_Pro.deleteExperience(idExperience);
		return true;
	}

	/**
	 * Méthode qui permet d'effacer une etude (ainsi que l'ecole et la
	 * spécialité concernée).
	 */
	public boolean deleteEtude(long idEtude) {
		Etude.deleteEtude(idEtude);
		return true;
	}

	/**
	 * Méthode qui permet d'effacer un message.
	 */
	public boolean deleteMessage(long idMessage) {
		MessagePrive.deleteMessage(idMessage);
		return true;
	}

	/**
	 * Méthode qui efface l'utilisateur dont l'email est passé en paramètre.
	 */
	public boolean deleteUser(String user_email) {
		Utilisateur.deleteUser(user_email);
		return true;
	}
	
	public String getIdentite (String email) {
		Entity user = Utilisateur.getUtilisateur(email);
		return user.getProperty("prenom").toString() + " " + user.getProperty("nom").toString();
	}

}
